package com.ctrip.corp.obt.shard.domain;

import com.alibaba.druid.pool.DruidDataSource;
import com.ctrip.corp.obt.generic.utils.EnvironmentHolder;
import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.shard.configuration.Config;
import com.ctrip.corp.obt.shard.configuration.DruidDatabaseProperties;
import com.ctrip.corp.obt.shard.constants.DbRole;
import com.ctrip.corp.obt.shard.constants.DbType;
import com.ctrip.corp.obt.shard.domain.dbmetainfo.DatabaseMetaInfo;
import com.ctrip.corp.obt.shard.exception.ShardException;
import com.ctrip.corp.obt.shard.holder.ShardSourceHolder;
import com.ctrip.corp.obt.shard.sql.mybatis.interceptor.inner.DatabaseCompatibilityInnerPlugin;
import com.ctrip.corp.obt.shard.utils.JdbcUtils;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Collections;
import java.util.List;

/**
 * @author marco.zheng
 * @version 1.0
 * @date 2022/01/14
 * @desc 数据库信息
 * @see
 * @since 1.0
 */
@Data
@Accessors(chain = true)
@NoArgsConstructor
public class ShardDataSourceMeta {

    public ShardDataSourceMeta(String driverClassName, String url, String username, String password,
        List<String> enablePlugins) {
        this.driverClass = driverClassName;
        this.url = url;
        this.username = username;
        this.password = password;
        this.dbType = JdbcUtils.getDbType(url).getValue();
        this.enablePlugins = enablePlugins;
    }

    private String driverClass;

    private String url;

    private String username;

    private String password;

    private int initialSize = 10;

    private int maxActive = 30;

    private int minIdle = 20;

    private long maxWait = 30000;

    private long timeBetweenEvictionRunsMillis = 10000;

    private String validatingQuery = "select 1";

    private boolean testWhileIdle = true;

    /**
     * 需要开启的插件配置
     */
    private List<String> enablePlugins;

    private DbRole role = DbRole.READ_WRITE;

    private Integer dbType;

    public DataSource buildDatasource(String tenantId) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(this.getUrl());
        dataSource.setUsername(this.getUsername());
        // TODO 后期的db密码都需要加密
        dataSource.setPassword(this.getPassword());
        dataSource.setAsyncInit(true);
        dataSource.setDriverClassName(this.getDriverClass());

        // 设置数据库其他连接属性
        // todo 不使用该方式，直接使用注入的方式获取DruidDatabaseProperties，下期改掉（注意：若是etcd平台接入参数设置，那么需要重新考虑这种方式是否合适）
        DruidDatabaseProperties dbProperties = EnvironmentHolder.getBean(DruidDatabaseProperties.class);
        setDbProperties(dbProperties);

        dataSource.setInitialSize(this.getInitialSize());
        dataSource.setMaxActive(this.getMaxActive());
        dataSource.setMinIdle(this.getMinIdle());
        dataSource.setMaxWait(this.getMaxWait());
        dataSource.setValidationQuery(this.getValidatingQuery());
        dataSource.setTestWhileIdle(this.isTestWhileIdle());
        dataSource.setTimeBetweenEvictionRunsMillis(this.getTimeBetweenEvictionRunsMillis());

        // 这里是数据库连接串的条件，可以不使用
        // String props = this.getConnectPropertiesJson();
        // if (StringUtils.isNotBlank(props)) {
        // JSONObject jsonObject = JSON.parseObject(props);
        // Properties properties = new Properties();
        // for (Map.Entry<String, Object> stringObjectEntry : jsonObject.entrySet()) {
        // properties.setProperty(String.valueOf(stringObjectEntry.getKey()),
        // String.valueOf(stringObjectEntry.getValue()));
        // }
        // dataSource.setConnectProperties(properties);
        // }

        // 失败后重连的次数
        dataSource.setConnectionErrorRetryAttempts(3);
        // 请求失败之后中断
        dataSource.setBreakAfterAcquireFailure(true);
        try {
            // init方法在DataSource初始化的时候并不会执行，是在第一次获取getConnection时执行
            dataSource.init();
            try (Connection connection = dataSource.getConnection()) {
                // apollo开关，主要是在项目启动过程中生效
                if (Boolean.TRUE.equals(Config.storeDatabaseDDL().enabled())) {
                    // 信创库搜集所有表信息字段
                    DbType databaseType = JdbcUtils.getDbType(this.getUrl());
                    if (DatabaseCompatibilityInnerPlugin.DB_TYPE_SET.contains(databaseType)
                        && !ShardSourceHolder.getXcDatabaseMetaInfoCache().containsKey(databaseType)) {
                        // 入本地缓存
                        ShardSourceHolder.putXcDatabaseMetaInfoCache(databaseType,
                            DatabaseMetaInfo.buildMetaInfoWithSql(connection, this.getUrl(), databaseType));
                    }
                }
            }
            return dataSource;
        } catch (Exception ex) {
            throw new ShardException(tenantId + " build data source error " + ex.getMessage());
        }
    }

    public ShardDataSourceMeta buildMeta(DruidDatabaseProperties dbProperties) {
        // 创建一个新的ShardDataSourceMeta实例
        this.driverClass = dbProperties.getDruidClassName();
        this.url = dbProperties.getUrl();
        this.username = dbProperties.getUsername();
        this.password = dbProperties.getPassword();
        this.dbType = JdbcUtils.getDbType(url).getValue();

        return this;
    }

    /**
     * 设置数据库连接属性
     *
     * @param dbProperties
     */
    private void setDbProperties(DruidDatabaseProperties dbProperties) {
        if (dbProperties.getInitialSize() > 0) {
            this.initialSize = dbProperties.getInitialSize();
        }
        if (dbProperties.getMaxActive() > 0) {
            this.maxActive = dbProperties.getMaxActive();
        }
        if (dbProperties.getMinIdle() > 0) {
            this.minIdle = dbProperties.getMinIdle();
        }
        if (dbProperties.getMaxWait() > 0) {
            this.maxWait = dbProperties.getMaxWait();
        }
        if (dbProperties.getTimeBetweenEvictionRunsMillis() > 0) {
            this.timeBetweenEvictionRunsMillis = dbProperties.getTimeBetweenEvictionRunsMillis();
        }
        if (StringUtils.isNotBlank(dbProperties.getValidatingQuery())) {
            this.validatingQuery = dbProperties.getValidatingQuery();
        }
        if (dbProperties.getTestWhileIdle() != null) {
            this.testWhileIdle = dbProperties.getTestWhileIdle();
        }
    }

    /**
     * 截取url中的片段：ip/database_name
     *
     * @return
     */
    public String extractUrlFragment() {
        int startIndex = url.indexOf("//") + 2;
        int endIndex = url.indexOf("?", startIndex);
        return url.substring(startIndex, endIndex);
    }

}
